package com.hmall.search.service.impl;

import cn.hutool.json.JSONUtil;
import com.hmall.common.domain.PageDTO;
import com.hmall.common.utils.CollUtils;
import com.hmall.search.domain.po.ItemDoc;
import com.hmall.search.domain.query.ItemPageQuery;
import com.hmall.search.domain.vo.CategoryAndBrandVo;
import com.hmall.search.service.ISearchService;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.lucene.search.function.CombineFunction;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Slf4j
@Service
public class SearchServiceImpl implements ISearchService {
    @Resource
    private RestHighLevelClient restHighLevelClient;

    @Override
    public PageDTO<ItemDoc> EsSearch(ItemPageQuery query) {
        //建立连接
        restHighLevelClient = new RestHighLevelClient(RestClient.builder(
                HttpHost.create("http://192.168.152.130:9200")
        ));
        PageDTO<ItemDoc> result = new PageDTO<>();
        // 1.构造请求
        SearchRequest request = new SearchRequest("items");
        // 2.构造查询条件
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        request.source().trackScores(true);
        if (query.getKey() != null && !query.getKey().isEmpty()) {
            boolQuery.must(QueryBuilders.matchQuery("name", query.getKey()));
        }
        // 高亮
        request.source().highlighter(SearchSourceBuilder.highlight()
                .field("name")
                .preTags("<em>")
                .postTags("</em>"));
        // 分页
        request.source().from(query.from()).size(query.getPageSize());
        // 排序
        if (!"".equals(query.getSortBy())){
            request.source().sort(query.getSortBy(), query.getIsAsc()? SortOrder.ASC : SortOrder.DESC);
        }else {
            request.source().sort("updateTime",query.getIsAsc()? SortOrder.ASC : SortOrder.DESC);
        }
        // 分类
        if (query.getCategory() != null && !query.getCategory().isEmpty()){
            boolQuery.filter(QueryBuilders.termQuery("category", query.getCategory()));
        }
        // 品牌
        if (query.getBrand() != null && !query.getBrand().isEmpty()){
            boolQuery.filter(QueryBuilders.termQuery("brand", query.getBrand()));
        }
        // 价格
        if (query.getMinPrice() != null && query.getMaxPrice() != null){
            boolQuery.filter(QueryBuilders.rangeQuery("price").gte(query.getMinPrice()).lte(query.getMaxPrice()));
        }
        //排名 广告优先
        request.source().query(QueryBuilders.functionScoreQuery(boolQuery,
                new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                        new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.termQuery("isAD", true),
                                ScoreFunctionBuilders.weightFactorFunction(100))
                }).boostMode(CombineFunction.MULTIPLY));
        // 3.发送请求
        try {
            SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
            // 解析结果
            result.setTotal(response.getHits().getTotalHits().value);
            result.setPages(response.getHits().getTotalHits().value % query.getPageSize() == 0 ? response.getHits().getTotalHits().value / query.getPageSize() : response.getHits().getTotalHits().value / query.getPageSize() + 1);
            SearchHit[] searchHits = response.getHits().getHits();
            List<ItemDoc> list = new ArrayList<>();
            for (SearchHit searchHit : searchHits) {
                ItemDoc bean = JSONUtil.toBean(searchHit.getSourceAsString(), ItemDoc.class);
                Map<String, HighlightField> highlightFields = searchHit.getHighlightFields();
                if(CollUtils.isNotEmpty(highlightFields)){
                    HighlightField name = highlightFields.get("name");
                    if(name != null){
                        String string = name.getFragments()[0].toString();
                        bean.setName(string);
                    }
                }
                list.add(bean);
            }
            result.setList(list);
        }catch (IOException e){
            log.error("查询ES失败,出现异常");
        }finally {
            try {
                restHighLevelClient.close();
            } catch (IOException e) {
                log.error("连接异常");
            }
        }
        
        return result;
    }

    @Override
    public CategoryAndBrandVo getFilters(ItemPageQuery query) {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        restHighLevelClient = new RestHighLevelClient(RestClient.builder(
                HttpHost.create("http://192.168.152.130:9200")
        ));
        CategoryAndBrandVo categoryAndBrandVo = new CategoryAndBrandVo();
        // 1.创建Request
        SearchRequest request = new SearchRequest("items");
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        if (query.getKey() != null && !query.getKey().isEmpty()) {
            boolQueryBuilder.must(QueryBuilders.matchQuery("name", query.getKey()));
        }
        if (query.getCategory() != null && !query.getCategory().isEmpty()){
            boolQueryBuilder.filter(QueryBuilders.termQuery("category", query.getCategory()));
        }
        if (query.getBrand() != null && !query.getBrand().isEmpty()){
            boolQueryBuilder.filter(QueryBuilders.termQuery("brand", query.getBrand()));
        }
        if (query.getMinPrice() != null && query.getMaxPrice()!=null){
            boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(query.getMinPrice()).lte(query.getMaxPrice()));
        }
        request.source().query(boolQueryBuilder).size(0);

        request.source().aggregation(
                AggregationBuilders.terms("category_agg").field("category").size(10)
        );

        request.source().aggregation(
                AggregationBuilders.terms("brand_agg").field("brand").size(10));
        List<String> categoryList = new ArrayList<>();
        List<String> brandList = new ArrayList<>();
        // 4.发送请求
        try {
            SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
            Aggregations aggregations = response.getAggregations();
            Terms categoryTerms = aggregations.get("category_agg");
            // 5.2.获取聚合中的桶
            List<? extends Terms.Bucket> buckets = categoryTerms.getBuckets();
            for (Terms.Bucket bucket : buckets) {
                // 5.4.获取桶内key
                String category = bucket.getKeyAsString();
                categoryList.add(category);
            }
            Terms brandTerms = aggregations.get("brand_agg");
            // 5.2.获取聚合中的桶
            List<? extends Terms.Bucket> buckets1 = brandTerms.getBuckets();
            for (Terms.Bucket bucket : buckets1) {
                // 5.4.获取桶内key
                String brand = bucket.getKeyAsString();
                brandList.add(brand);
            }
        } catch (IOException e) {
            log.error("发送请求异常");
        }
        categoryAndBrandVo.setCategory(categoryList);
        categoryAndBrandVo.setBrand(brandList);
        try {
            restHighLevelClient.close();
        } catch (IOException e) {
            log.error("关闭连接异常");
        }
        return categoryAndBrandVo;
    }
}
